﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.IO;
using System.Diagnostics;
using System.Data.SQLite;
using System.ComponentModel;

namespace KeyboardImageViewer
{
    public static class DuplicateUtils
    {
        public const int MetaPixelsPerSide = 15;
        public const int MetaPixels = MetaPixelsPerSide * MetaPixelsPerSide;
        public const int FeatureLength = MetaPixels * 3;
        public const int HashLength = 40;
        public const int PrefixLengthBytes = 3;
        
        private const double AverageBaseDevianceThreshold = 1.5;
        private const int ColorMaximumTolerance = 75;
        private const int PixelBorderDrasticThreshold = 50;
        private const double DrasticBorderFractionFloor = 0.07;

        public static ImageSummary GetImageSummary(string filePath)
        {
            try
            {
                if (filePath.ToLowerInvariant().EndsWith(".gif") && File.Exists(filePath))
                {
                    System.Drawing.Bitmap gifBitmap = null;

                    gifBitmap = new System.Drawing.Bitmap(filePath);
                    System.Drawing.Imaging.FrameDimension dimension = new System.Drawing.Imaging.FrameDimension(gifBitmap.FrameDimensionsList[0]);
                    int frameCount = gifBitmap.GetFrameCount(dimension);
                    gifBitmap.Dispose();

                    if (frameCount > 1)
                    {
                        // If this is animated, we do not track it for duplicates.
                        return GetIneligibleSummary();
                    }
                }

                byte[] thumbnail = new byte[FeatureLength];

                BitmapImage image = new BitmapImage();
                image.BeginInit();
                image.UriSource = new Uri(filePath);
                image.EndInit();

                if (image.PixelWidth < MetaPixelsPerSide || image.PixelHeight < MetaPixelsPerSide)
                {
                    return GetIneligibleSummary();
                }

                double metaPixelWidth = ((double)image.PixelWidth) / MetaPixelsPerSide;
                double metaPixelHeight = ((double)image.PixelHeight) / MetaPixelsPerSide;

                PixelFormat pixelFormat = image.Format;
                if (pixelFormat != PixelFormats.Bgr32 && pixelFormat != PixelFormats.Bgra32 && pixelFormat != PixelFormats.Gray8 && pixelFormat != PixelFormats.Indexed8)
                {
                    Debug.Assert(false, "Unrecognized pixel format " + pixelFormat.ToString() + " on image " + filePath);
                    return GetIneligibleSummary();
                }

                int bytesPerPixel = pixelFormat.BitsPerPixel / 8;

                byte[] imageData = new byte[image.PixelWidth * image.PixelHeight * bytesPerPixel];
                image.CopyPixels(imageData, image.PixelWidth * bytesPerPixel, 0);

                double leftBorder, rightBorder, topBorder, bottomBorder;
                int fullPixelLeft, fullPixelRight, fullPixelTop, fullPixelBottom;
                int bytesPerPixelRow = image.PixelWidth * bytesPerPixel;
                double metaPixelArea = metaPixelWidth * metaPixelHeight;
                int totalRed = 0, totalGreen = 0, totalBlue = 0;
                int summaryIndex = 0;

                for (int i = 0; i < MetaPixelsPerSide; i++)
                {
                    topBorder = i * metaPixelHeight;
                    bottomBorder = (i + 1) * metaPixelHeight;

                    if (i == MetaPixelsPerSide - 1)
                    {
                        bottomBorder = Math.Min(bottomBorder, image.PixelHeight);
                    }

                    double fractionTop = Math.Ceiling(topBorder) - topBorder;
                    if (fractionTop == 0)
                    {
                        fullPixelTop = (int)topBorder;
                    }
                    else
                    {
                        fullPixelTop = (int)Math.Ceiling(topBorder);
                    }

                    double fractionBottom = bottomBorder - Math.Floor(bottomBorder);

                    fullPixelBottom = ((int)bottomBorder) - 1;

                    for (int j = 0; j < MetaPixelsPerSide; j++)
                    {
                        leftBorder = j * metaPixelWidth;
                        rightBorder = (j + 1) * metaPixelWidth;

                        if (j == MetaPixelsPerSide - 1)
                        {
                            rightBorder = Math.Min(rightBorder, image.PixelWidth);
                        }

                        double fractionLeft = Math.Ceiling(leftBorder) - leftBorder;
                        if (fractionLeft == 0)
                        {
                            fullPixelLeft = (int)leftBorder;
                        }
                        else
                        {
                            fullPixelLeft = (int)Math.Ceiling(leftBorder);
                        }

                        double fractionRight = rightBorder - Math.Floor(rightBorder);
                        fullPixelRight = ((int)rightBorder) - 1;

                        double metaPixelRed = 0;
                        double metaPixelGreen = 0;
                        double metaPixelBlue = 0;

                        // Calculate top pixels
                        if (fractionTop != 0)
                        {
                            int pixelAddress = ((fullPixelTop - 1) * image.PixelWidth + fullPixelLeft) * bytesPerPixel;

                            for (int k = fullPixelLeft; k <= fullPixelRight; k++)
                            {
                                AddColorFromAddress(imageData, image, pixelAddress, fractionTop, ref metaPixelBlue, ref metaPixelGreen, ref metaPixelRed);
                                pixelAddress += bytesPerPixel;
                            }
                        }

                        // Bottom pixels
                        if (fractionBottom != 0)
                        {
                            int pixelAddress = ((fullPixelBottom + 1) * image.PixelWidth + fullPixelLeft) * bytesPerPixel;

                            for (int k = fullPixelLeft; k <= fullPixelRight; k++)
                            {
                                AddColorFromAddress(imageData, image, pixelAddress, fractionBottom, ref metaPixelBlue, ref metaPixelGreen, ref metaPixelRed);
                                pixelAddress += bytesPerPixel;
                            }
                        }

                        // Left pixels
                        if (fractionLeft != 0)
                        {
                            int pixelAddress = (fullPixelTop * image.PixelWidth + fullPixelLeft - 1) * bytesPerPixel;

                            for (int k = fullPixelTop; k <= fullPixelBottom; k++)
                            {
                                AddColorFromAddress(imageData, image, pixelAddress, fractionLeft, ref metaPixelBlue, ref metaPixelGreen, ref metaPixelRed);
                                pixelAddress += bytesPerPixelRow;
                            }
                        }

                        // Right pixels
                        if (fractionRight != 0)
                        {
                            int pixelAddress = (fullPixelTop * image.PixelWidth + fullPixelRight + 1) * bytesPerPixel;

                            for (int k = fullPixelTop; k <= fullPixelBottom; k++)
                            {
                                AddColorFromAddress(imageData, image, pixelAddress, fractionRight, ref metaPixelBlue, ref metaPixelGreen, ref metaPixelRed);
                                pixelAddress += bytesPerPixelRow;
                            }
                        }

                        // Corner pixels

                        if (fractionTop != 0)
                        {
                            if (fractionLeft != 0)
                            {
                                int pixelAddress = ((fullPixelTop - 1) * image.PixelWidth + fullPixelLeft - 1) * bytesPerPixel;
                                AddColorFromAddress(imageData, image, pixelAddress, fractionTop * fractionLeft, ref metaPixelBlue, ref metaPixelGreen, ref metaPixelRed);
                            }

                            if (fractionRight != 0)
                            {
                                int pixelAddress = ((fullPixelTop - 1) * image.PixelWidth + fullPixelRight + 1) * bytesPerPixel;
                                AddColorFromAddress(imageData, image, pixelAddress, fractionTop * fractionRight, ref metaPixelBlue, ref metaPixelGreen, ref metaPixelRed);
                            }
                        }

                        if (fractionBottom != 0)
                        {
                            if (fractionLeft != 0)
                            {
                                int pixelAddress = ((fullPixelBottom + 1) * image.PixelWidth + fullPixelLeft - 1) * bytesPerPixel;
                                AddColorFromAddress(imageData, image, pixelAddress, fractionBottom * fractionLeft, ref metaPixelBlue, ref metaPixelGreen, ref metaPixelRed);
                            }

                            if (fractionRight != 0)
                            {
                                int pixelAddress = ((fullPixelBottom + 1) * image.PixelWidth + fullPixelRight + 1) * bytesPerPixel;
                                AddColorFromAddress(imageData, image, pixelAddress, fractionBottom * fractionRight, ref metaPixelBlue, ref metaPixelGreen, ref metaPixelRed);
                            }
                        }

                        // Calculate interior

                        int metaPixelInteriorRed = 0;
                        int metaPixelInteriorGreen = 0;
                        int metaPixelInteriorBlue = 0;

                        for (int k = fullPixelTop; k <= fullPixelBottom; k++)
                        {
                            int pixelAddress = (k * image.PixelWidth + fullPixelLeft) * bytesPerPixel;

                            for (int l = fullPixelLeft; l <= fullPixelRight; l++)
                            {
                                AddColorFromAddress(imageData, image, pixelAddress, ref metaPixelInteriorBlue, ref metaPixelInteriorGreen, ref metaPixelInteriorRed);
                                pixelAddress += bytesPerPixel;
                            }
                        }

                        metaPixelRed += metaPixelInteriorRed;
                        metaPixelGreen += metaPixelInteriorGreen;
                        metaPixelBlue += metaPixelInteriorBlue;

                        metaPixelRed /= metaPixelArea;
                        metaPixelGreen /= metaPixelArea;
                        metaPixelBlue /= metaPixelArea;

                        byte red = (byte)metaPixelRed;
                        byte green = (byte)metaPixelGreen;
                        byte blue = (byte)metaPixelBlue;

                        totalRed += red;
                        totalGreen += green;
                        totalBlue += blue;

                        thumbnail[summaryIndex * 3] = red;
                        thumbnail[summaryIndex * 3 + 1] = green;
                        thumbnail[summaryIndex * 3 + 2] = blue;

                        summaryIndex++;
                    }
                }

                ImageSummary summary = new ImageSummary { Thumbnail = thumbnail };

                // Calculate the prefix of the image signature.
                byte[] fingerprintHash = MatrixOperations.CalculateHash(thumbnail);

                StringBuilder prefixKeyBuilder = new StringBuilder();

                for (int i = 0; i < PrefixLengthBytes; i++)
                {
                    prefixKeyBuilder.Append(fingerprintHash[i].ToString("x2"));
                }

                summary.HashPrefix = prefixKeyBuilder.ToString();

                byte averageRed = (byte)(totalRed / MetaPixels);
                byte averageGreen = (byte)(totalGreen / MetaPixels);
                byte averageBlue = (byte)(totalBlue / MetaPixels);

                // Find average variance from the mean color

                int totalVariance = 0;
                for (int i = 0; i < MetaPixelsPerSide; i++)
                {
                    for (int j = 0; j < MetaPixelsPerSide; j++)
                    {
                        totalVariance += Math.Abs(summary[j, i, 0] - averageRed);
                        totalVariance += Math.Abs(summary[j, i, 1] - averageGreen);
                        totalVariance += Math.Abs(summary[j, i, 2] - averageBlue);
                    }
                }

                summary.AverageVariance = ((double)totalVariance) / MetaPixels;

                // Find the fraction of drastic pixel borders

                int numDrasticBorders = 0;
                for (int i = 0; i < MetaPixelsPerSide; i++)
                {
                    for (int j = 0; j < MetaPixelsPerSide - 1; j++)
                    {
                        int borderVariance = 0;
                        borderVariance += Math.Abs(summary[j, i, 0] - summary[j + 1, i, 0]);
                        borderVariance += Math.Abs(summary[j, i, 1] - summary[j + 1, i, 1]);
                        borderVariance += Math.Abs(summary[j, i, 2] - summary[j + 1, i, 2]);

                        if (borderVariance > PixelBorderDrasticThreshold)
                        {
                            numDrasticBorders++;
                        }
                    }
                }

                for (int i = 0; i < MetaPixelsPerSide; i++)
                {
                    for (int j = 0; j < MetaPixelsPerSide - 1; j++)
                    {
                        int borderVariance = 0;
                        borderVariance += Math.Abs(summary[i, j, 0] - summary[i, j + 1, 0]);
                        borderVariance += Math.Abs(summary[i, j, 1] - summary[i, j + 1, 1]);
                        borderVariance += Math.Abs(summary[i, j, 2] - summary[i, j + 1, 2]);

                        if (borderVariance > PixelBorderDrasticThreshold)
                        {
                            numDrasticBorders++;
                        }
                    }
                }

                int totalBorders = MetaPixels * 2 - MetaPixelsPerSide * 2;
                summary.FractionDrasticPixelBorders = ((double)numDrasticBorders) / totalBorders;

                return summary;
            }
            catch (System.IO.FileFormatException)
            {
                return GetIneligibleSummary();
            }
            catch (NotSupportedException)
            {
                return GetIneligibleSummary();
            }
        }

        private static void AddColorFromAddress(byte[] imageData, BitmapImage image, int pixelAddress, double fraction, ref double blue, ref double green, ref double red)
        {
            PixelFormat pixelFormat = image.Format;

            if (pixelFormat == PixelFormats.Bgr32 || pixelFormat == PixelFormats.Bgra32)
            {
                blue += fraction * imageData[pixelAddress];
                green += fraction * imageData[pixelAddress + 1];
                red += fraction * imageData[pixelAddress + 2];
            }
            else if (pixelFormat == PixelFormats.Gray8)
            {
                blue += fraction * imageData[pixelAddress];
                green += fraction * imageData[pixelAddress];
                red += fraction * imageData[pixelAddress];
            }
            else if (pixelFormat == PixelFormats.Indexed8)
            {
                int colorIndex = (int)imageData[pixelAddress];

                Color pixelColor = image.Palette.Colors[colorIndex];

                blue += fraction * pixelColor.B;
                green += fraction * pixelColor.G;
                red += fraction * pixelColor.R;
            }
        }

        private static void AddColorFromAddress(byte[] imageData, BitmapImage image, int pixelAddress, ref int blue, ref int green, ref int red)
        {
            PixelFormat pixelFormat = image.Format;

            if (pixelFormat == PixelFormats.Bgr32 || pixelFormat == PixelFormats.Bgra32)
            {
                blue += imageData[pixelAddress];
                green += imageData[pixelAddress + 1];
                red += imageData[pixelAddress + 2];
            }
            else if (pixelFormat == PixelFormats.Gray8)
            {
                blue += imageData[pixelAddress];
                green += imageData[pixelAddress];
                red += imageData[pixelAddress];
            }
            else if (pixelFormat == PixelFormats.Indexed8)
            {
                int colorIndex = (int)imageData[pixelAddress];

                Color pixelColor = image.Palette.Colors[colorIndex];

                blue += pixelColor.B;
                green += pixelColor.G;
                red += pixelColor.R;
            }
        }

        public static List<int> FindDuplicatesForImage(int imageId, SQLiteConnection connection)
        {
            var duplicates = new List<int>();

            ImageSummary currentImage;

            SQLiteCommand getPrefix = new SQLiteCommand("SELECT * FROM summaries WHERE ineligible = 0 AND imageId = " + imageId, connection);
            using (SQLiteDataReader reader = getPrefix.ExecuteReader())
            {
                if (!reader.Read())
                {
                    return duplicates;
                }

                currentImage = GetSummaryFromReader(reader);
            }

            SQLiteCommand getPrefixMatches = new SQLiteCommand("SELECT thumbnail, imageId, averageVariance, fractionDrasticPixelBorders, hashPrefix, imageId FROM summaries, images WHERE images.id = summaries.imageId AND included = 1 AND ineligible = 0 AND hashPrefix = '" + currentImage.HashPrefix + "'", connection);

            using (SQLiteDataReader reader = getPrefixMatches.ExecuteReader())
            {
                while (reader.Read())
                {
                    ImageSummary duplicateCandidate = GetSummaryFromReader(reader);
                    int duplicateCandidateId = reader.GetInt32("imageId");

                    if (imageId != duplicateCandidateId && AreDuplicates(currentImage, duplicateCandidate))
                    {
                        duplicates.Add(duplicateCandidateId);
                    }
                }
            }

            return duplicates;
        }

        public static List<List<int>> FindAllDuplicates(SQLiteConnection connection)
        {
            List<List<int>> duplicateGroups = new List<List<int>>();

            SQLiteCommand getPrefixGroups = new SQLiteCommand("SELECT COUNT(*) AS numItems, hashPrefix FROM summaries, images WHERE images.id = summaries.imageId AND included = 1 AND ineligible = 0 GROUP BY hashPrefix HAVING numItems > 1", connection);
            SQLiteCommand getSummaryDetail = new SQLiteCommand("SELECT thumbnail, imageId, averageVariance, fractionDrasticPixelBorders, hashPrefix FROM summaries WHERE ineligible = 0 AND hashPrefix = ?", connection);
            //SQLiteCommand getSummaryDetail = new SQLiteCommand("SELECT thumbnail, imageId, averageVariance, fractionDrasticPixelBorders, hashPrefix FROM summaries, images WHERE images.id = summaries.imageId AND included = 1 AND ineligible = 0 AND hashPrefix = ?", connection);
            var hashPrefixParam = getSummaryDetail.Parameters.Add("hashPrefix", System.Data.DbType.String);

            using (SQLiteDataReader groupReader = getPrefixGroups.ExecuteReader())
            {
                Stopwatch getSummaryTime = new Stopwatch();

                while (groupReader.Read())
                {
                    hashPrefixParam.Value = groupReader.GetString("hashPrefix");

                    List<ImageSummary> duplicateCandidates = new List<ImageSummary>();

                    getSummaryTime.Start();

                    using (SQLiteDataReader summaryReader = getSummaryDetail.ExecuteReader())
                    {
                        while (summaryReader.Read())
                        {
                            duplicateCandidates.Add(GetSummaryFromReader(summaryReader));
                        }
                    }

                    getSummaryTime.Stop();

                    List<List<int>> sectionDuplicateGroups = FindDuplicatesInList(duplicateCandidates);

                    foreach (List<int> duplicateGroup in sectionDuplicateGroups)
                    {
                        duplicateGroups.Add(duplicateGroup);
                    }
                }

                Debug.WriteLine("Get summary time: " + getSummaryTime.Elapsed.ToString());
            }

            return duplicateGroups;
        }

        private static List<List<int>> FindDuplicatesInList(List<ImageSummary> duplicateCandidates)
        {
            var duplicates = new List<List<int>>();

            int numItems = duplicateCandidates.Count;

            for (int i = 0; i < duplicateCandidates.Count - 1; i++)
            {
                bool leftSummaryIsDuplicate = false;
                int leftSummaryDuplicateGroupIndex = -1;

                for (int j = i + 1; j < duplicateCandidates.Count; j++)
                {
                    if (AreDuplicates(duplicateCandidates[i], duplicateCandidates[j]))
                    {
                        if (!leftSummaryIsDuplicate)
                        {
                            // See if the left duplicate is already in the duplicates list.
                            for (int k = 0; k < duplicates.Count && !leftSummaryIsDuplicate; k++)
                            {
                                for (int l = 0; l < duplicates[k].Count && !leftSummaryIsDuplicate; l++)
                                {
                                    if (duplicateCandidates[i].Id == duplicates[k][l])
                                    {
                                        leftSummaryIsDuplicate = true;
                                        leftSummaryDuplicateGroupIndex = k;
                                    }
                                }
                            }
                        }

                        // See if the right duplicate is already in the duplicates list.
                        bool rightSummaryIsDuplicate = false;
                        int rightSummaryDuplicateGroupIndex = -1;

                        for (int k = 0; k < duplicates.Count && !rightSummaryIsDuplicate; k++)
                        {
                            for (int l = 0; l < duplicates[k].Count && !rightSummaryIsDuplicate; l++)
                            {
                                if (duplicateCandidates[j].Id == duplicates[k][l])
                                {
                                    rightSummaryIsDuplicate = true;
                                    rightSummaryDuplicateGroupIndex = k;
                                }
                            }
                        }

                        if (leftSummaryIsDuplicate && !rightSummaryIsDuplicate)
                        {
                            duplicates[leftSummaryDuplicateGroupIndex].Add(duplicateCandidates[j].Id);
                        }
                        else if (!leftSummaryIsDuplicate && rightSummaryIsDuplicate)
                        {
                            duplicates[rightSummaryDuplicateGroupIndex].Add(duplicateCandidates[i].Id);
                        }
                        else if (!leftSummaryIsDuplicate && !rightSummaryIsDuplicate)
                        {
                            int newGroupIndex = duplicates.Count;
                            List<int> newGroup = new List<int>();
                            newGroup.Add(duplicateCandidates[i].Id);
                            newGroup.Add(duplicateCandidates[j].Id);
                            duplicates.Add(newGroup);

                            leftSummaryIsDuplicate = true;
                            leftSummaryDuplicateGroupIndex = newGroupIndex;
                        }
                        else
                        {
                            // If they are both already in the list somewhere but not in the same group, combine them.
                            if (leftSummaryDuplicateGroupIndex != rightSummaryDuplicateGroupIndex)
                            {
                                foreach (int rightSummaryId in duplicates[rightSummaryDuplicateGroupIndex])
                                {
                                    duplicates[leftSummaryDuplicateGroupIndex].Add(rightSummaryId);
                                }

                                duplicates.RemoveAt(rightSummaryDuplicateGroupIndex);
                            }
                        }
                    }
                }
            }

            return duplicates;
        }

        public static List<List<int>> FindAllExactDuplicates(SQLiteConnection connection)
        {
            var duplicates = new List<List<int>>();

            List<string> duplicateHashes = new List<string>();

            SQLiteCommand findDuplicatesCommand = new SQLiteCommand("SELECT hash, COUNT(*) as numDuplicates FROM images WHERE included = 1 GROUP BY hash HAVING numDuplicates > 1", connection);
            using (SQLiteDataReader reader = findDuplicatesCommand.ExecuteReader())
            {
                while (reader.Read())
                {
                    duplicateHashes.Add(reader.GetString("hash"));
                }
            }

            SQLiteCommand getImagesInDuplicateGroupCommand = new SQLiteCommand("SELECT id FROM images WHERE hash = ? AND included = 1", connection);
            SQLiteParameter getImageHashParam = getImagesInDuplicateGroupCommand.Parameters.Add("hash", System.Data.DbType.String);

            foreach (string duplicateHash in duplicateHashes)
            {
                var duplicateGroup = new List<int>();
                getImageHashParam.Value = duplicateHash;

                using (SQLiteDataReader reader = getImagesInDuplicateGroupCommand.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        duplicateGroup.Add(reader.GetInt32("id"));
                    }
                }

                duplicates.Add(duplicateGroup);
            }

            return duplicates;
        }

        /// <summary>
        /// Returns true if the given summaries are duplicates.
        /// </summary>
        /// <remarks>Assumes that summaryA is not corrupt.</remarks>
        /// <param name="summaryA">The first summary to compare.</param>
        /// <param name="summaryB">The second summary to compare.</param>
        /// <returns>True if the given summaries are duplicates.</returns>
        private static bool AreDuplicates(ImageSummary summaryA, ImageSummary summaryB)
        {
            bool match = true;
            int totalDeviance = 0;

            for (int x = 0; x < DuplicateUtils.MetaPixelsPerSide && match; x++)
            {
                for (int y = 0; y < DuplicateUtils.MetaPixelsPerSide && match; y++)
                {
                    int redDeviance = Math.Abs(summaryA[x, y, 0] - summaryB[x, y, 0]);
                    int greenDeviance = Math.Abs(summaryA[x, y, 1] - summaryB[x, y, 1]);
                    int blueDeviance = Math.Abs(summaryA[x, y, 2] - summaryB[x, y, 2]);

                    if (redDeviance > ColorMaximumTolerance ||
                        greenDeviance > ColorMaximumTolerance ||
                        blueDeviance > ColorMaximumTolerance)
                    {
                        match = false;
                    }

                    totalDeviance += redDeviance;
                    totalDeviance += greenDeviance;
                    totalDeviance += blueDeviance;
                }
            }

            if (match)
            {
                double averageDeviance = ((double)totalDeviance) / MetaPixels;
                double maxVariance = Math.Max(summaryA.AverageVariance, summaryB.AverageVariance);
                double maxFractionDrasticBorders = Math.Max(summaryA.FractionDrasticPixelBorders, summaryB.FractionDrasticPixelBorders);
                double averageDevianceThreshold = AverageBaseDevianceThreshold * maxVariance * Math.Max(maxFractionDrasticBorders, DrasticBorderFractionFloor);

                if (averageDeviance <= averageDevianceThreshold)
                {
                    return true;
                }
            }

            return false;
        }

        private static ImageSummary GetSummaryFromReader(SQLiteDataReader reader)
        {
            ImageSummary summary = new ImageSummary
            {
                Id = reader.GetInt32("imageId"),
                AverageVariance = reader.GetDouble("averageVariance"),
                FractionDrasticPixelBorders = reader.GetDouble("fractionDrasticPixelBorders"),
                HashPrefix = reader.GetString("hashPrefix")
            };

            byte[] thumbnail = new byte[FeatureLength];
            reader.GetBytes("thumbnail", 0, thumbnail, 0, FeatureLength);
            summary.Thumbnail = thumbnail;

            return summary;
        }

        public static List<int> FindExactDuplicates(SQLiteConnection connection, int imageId)
        {
            var duplicates = new List<int>();

            SQLiteCommand getReferenceImage = new SQLiteCommand("SELECT hash FROM images WHERE id = " + imageId + " AND included", connection);
            using (SQLiteDataReader referenceReader = getReferenceImage.ExecuteReader())
            {
                if (referenceReader.Read())
                {
                    SQLiteCommand getDuplicateImages = new SQLiteCommand("SELECT id FROM images WHERE hash = '" + referenceReader.GetString("hash") + "' AND id != " + imageId + " AND included", connection);
                    using (SQLiteDataReader duplicateReader = getDuplicateImages.ExecuteReader())
                    {
                        while (duplicateReader.Read())
                        {
                            duplicates.Add(duplicateReader.GetInt32("id"));
                        }
                    }
                }
            }

            return duplicates;
        }

        private static ImageSummary GetIneligibleSummary()
        {
            return new ImageSummary { Ineligible = true };
        }
    }
}
